import { $set } from '@feng3d/serialization';
import { BufferGeometry3D, Mesh3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, Scene3D, StatsComponent, TextureLoader3D, WebGLRenderer3D } from 'feng3d-three';
import { ParametricGeometries3D } from '../../extension/geometries/ParametricGeometries3D';
import { ParametricGeometry3D } from '../../extension/geometries/ParametricGeometry3D';
import { Curves } from '../../extension/thirdparty/three/imports';

const rootPath = '../../';

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let stats: StatsComponent;

init();
animate();

function init()
{
    const container = document.getElementById('container');

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 1, far: 2000 });
    camera.entity.position.y = 400;

    scene = new Node3D().addComponent('Scene3D');

    //

    scene.addComponent('AmbientLight3D', { color: { value: 0xcccccc }, intensity: 0.4 });

    const pointLight = new Node3D().addComponent('PointLight3D', { color: { value: 0xffffff }, intensity: 0.8 });
    camera.entity.addChild(pointLight.entity);
    scene.entity.addChild(camera.entity);

    //

    const map = new TextureLoader3D().load(`${rootPath}textures/uv_grid_opengl.jpg`);
    map.wrapS = map.wrapT = 'RepeatWrapping';
    map.anisotropy = 16;

    const material = $set(new MeshPhongMaterial3D(), { map, side: 'DoubleSide' });

    //

    let geometry: BufferGeometry3D;
    let object: Mesh3D;

    geometry = $set(new ParametricGeometry3D(), { func: ParametricGeometries3D.plane(100, 100), slices: 10, stacks: 10 });
    geometry.center();
    object = new Node3D().addComponent('Mesh3D', { geometry, material });
    object.entity.position.set(-200, 0, 200);
    scene.entity.addChild(object.entity);

    geometry = $set(new ParametricGeometry3D(), { func: ParametricGeometries3D.klein, slices: 20, stacks: 20 });
    object = new Node3D().addComponent('Mesh3D', { geometry, material });
    object.entity.position.set(0, 0, 200);
    object.entity.scale.multiplyScalar(5);
    scene.entity.addChild(object.entity);

    geometry = $set(new ParametricGeometry3D(), { func: ParametricGeometries3D.mobius, slices: 20, stacks: 20 });
    object = new Node3D().addComponent('Mesh3D', { geometry, material });
    object.entity.position.set(200, 0, 200);
    object.entity.scale.multiplyScalar(30);
    scene.entity.addChild(object.entity);

    //

    const GrannyKnot = new Curves.GrannyKnot();

    const torus = $set(new ParametricGeometries3D.TorusKnotGeometry3D(), { radius: 50, tube: 10, segmentsT: 50, segmentsR: 20, p: 2, q: 3 });
    const sphere = $set(new ParametricGeometries3D.SphereGeometry3D(), { size: 50, u: 20, v: 10 });
    const tube = $set(new ParametricGeometries3D.TubeGeometry3D(), { path: GrannyKnot, segments: 100, radius: 3, segmentsRadius: 8, closed: true });

    object = new Node3D().addComponent('Mesh3D', { geometry: torus, material });
    object.entity.position.set(-200, 0, -200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', { geometry: sphere, material });
    object.entity.position.set(0, 0, -200);
    scene.entity.addChild(object.entity);

    object = new Node3D().addComponent('Mesh3D', { geometry: tube, material });
    object.entity.position.set(200, 0, -200);
    object.entity.scale.multiplyScalar(2);
    scene.entity.addChild(object.entity);

    //

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    const timer = Date.now() * 0.0001;

    camera.entity.position.x = Math.cos(timer) * 800;
    camera.entity.position.z = Math.sin(timer) * 800;

    camera.entity.lookAt(scene.entity.position);

    scene.entity.traverse(function (object: Node3D)
    {
        if (object.isMesh === true)
        {
            object.rotation.x = timer * 5;
            object.rotation.y = timer * 2.5;
        }
    });

    renderer.render(scene, camera);
}
